package com.smartalarmclock.Model;

import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.smartalarmclock.Model.Exceptions.DataEntityDoesNotExistException;

final class TimeFrame extends ReferencedDataEntity implements ITimeFrame {

	// Database constants
	
	public static final String TimeFrameTableName = "TimeFrame";
	
	private static final String c_TimeFrameColumnNameStartTime = "StartTime";
	private static final String c_TimeFrameColumnNameEndTime = "EndTime";
	
	private static final String[] c_QueryAllColumns = new String[] {
		c_ColumnNameId, c_TimeFrameColumnNameStartTime, c_TimeFrameColumnNameEndTime };
	
	// Members
	
	private TimeInWeek m_StartTime;
	private TimeInWeek m_EndTime;
	
	// Constructors
	
	/**
	 * Constructor
	 * @param startTime the start time
	 * @param endTime the end time
	 */
	protected TimeFrame(TimeInWeek startTime, TimeInWeek endTime) {
		super();
		Initialize(startTime, endTime);
	}
	
	/**
	 * Constructor
	 * @param id the id
	 * @param startTime the start time
	 * @param endTime the end time
	 */
	private TimeFrame(int id, TimeInWeek startTime, TimeInWeek endTime) {
		super(id);
		Initialize(startTime, endTime);
	}
	
	// ITimeFrame implementation

	@Override
	public TimeInWeek GetStartTime() {
		return m_StartTime;
	}

	@Override
	public void SetStartTime(TimeInWeek startTime) {
		m_StartTime = startTime;
		SetUpdated();
	}

	@Override
	public TimeInWeek GetEndTime() {
		return m_EndTime;
	}

	@Override
	public void SetEndTime(TimeInWeek endTime) {
		m_EndTime = endTime;
		SetUpdated();
	}
	
	// Public Methods
	
	/**
	 * @return the CREATE TABLE statement of the TimeFrame table
	 */
	static String GetSqlStatementCreateTimeFrameTable() {
		return "CREATE TABLE " + TimeFrameTableName + " (" +
				c_ColumnNameId + 					" INTEGER PRIMARY KEY, " +
				c_TimeFrameColumnNameStartTime + 	" TEXT NOT NULL, " +
				c_TimeFrameColumnNameEndTime + 		" TEXT NOT NULL)";
	}
	
	/**
	 * @return the DROP TABLE statement of the TimeFrame table
	 */
	static String GetSqlStatementDropTimeFrameTable() {
		return "DROP TABLE IF EXISTS " + TimeFrameTableName;
	}
	
	/**
	 * Queries a TimeFrame instance with the given id
	 * @param context the context of the request
	 * @param id the id to query
	 * @return the matching TimeFrame
	 * @throws DataEntityDoesNotExistException if the matching TimeFrame does not exist
	 */
	static TimeFrame GetTimeFrameById(Context context, int id) throws DataEntityDoesNotExistException {
		return GetSingleTimeFrame(context, c_ColumnNameId + "=" + id);
	}
	
	/**
	 * Queries a TimeFrame instance with the given id
	 * @param context the context of the request
	 * @param id the id to query
	 * @return the matching TimeFrame or null if it does not exist
	 */
	static TimeFrame TryGetTimeFrameById(Context context, int id) {
		try {
			return GetTimeFrameById(context, id);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	/**
	 * Queries time frames matching the given time range.
	 * @param context the context of the request
	 * @return a collection of time frames matching the given time range.
	 */
	static Set<ITimeFrame> GetTimeFramesByTimeRange(Context context, TimeInWeek startTime, TimeInWeek endTime) {
		Date startDateFromSunday = startTime.CalculateDate(false);
		Date endDateFromSunday = endTime.CalculateDate(false);
		if (endDateFromSunday.getTime() < startDateFromSunday.getTime()) {
			endDateFromSunday = endTime.CalculateDate(true);
		}
		
		SQLiteDatabase db = OpenDBForReading(context);
		Cursor cursor = db.query(
			TimeFrameTableName,
			c_QueryAllColumns,
			// Start time in range is between the TimeFrame
			"((" + c_TimeFrameColumnNameStartTime + "<=" + startDateFromSunday.getTime() + " AND " +
					startDateFromSunday.getTime() + "<=" + c_TimeFrameColumnNameEndTime + ")) OR" +
			// Or end time range is between the TimeFrame
			"((" + c_TimeFrameColumnNameStartTime + "<=" + endDateFromSunday.getTime() + " AND " +
					endDateFromSunday.getTime() + "<=" + c_TimeFrameColumnNameEndTime + ")) OR" +
			// Start time in range is before the TimeFrame and the end time is after the TimeFrame
			"((" + startDateFromSunday.getTime() + "<=" + c_TimeFrameColumnNameStartTime + " AND " +
					c_TimeFrameColumnNameEndTime + "<=" + endDateFromSunday.getTime() + "))",
			null, null, null, null);
		
		Set<ITimeFrame> timeFrames = new HashSet<ITimeFrame>();
		while (cursor.moveToNext()) {
			timeFrames.add(CreateTimeFrameFromeCursor(cursor));
		}
		
		cursor.close();
		db.close();
		return timeFrames;
	}
	
	// Private Methods
	
	/**
	 * Queries a single time frame from the DB using the given where clause
	 * @param context the context of the request
	 * @param whereClause the where clause to use
	 * @return a matching TimeFrame instance
	 * @throws DataEntityDoesNotExistException if the matching time frame does not exist
	 */
	private static TimeFrame GetSingleTimeFrame(Context context, String whereClause) throws DataEntityDoesNotExistException {
		SQLiteDatabase db = OpenDBForReading(context);
		Cursor cursor = db.query(
			TimeFrameTableName,
			c_QueryAllColumns,
			whereClause,
			null, null, null, null);
		
		try {
			if (!cursor.moveToNext()) {
				throw new DataEntityDoesNotExistException(TimeFrame.class, whereClause);
			}
			
			return CreateTimeFrameFromeCursor(cursor);
		}
		finally {
			cursor.close();
			db.close();
		}
	}
	
	/**
	 * Creates a TimeFrame instance from the cursor containing the values
	 * @param cursor the cursor with the time frame values
	 * @return a matching TimeFrame instance
	 */
	private static TimeFrame CreateTimeFrameFromeCursor(Cursor cursor) {
		int id = cursor.getInt(cursor.getColumnIndex(c_ColumnNameId));
		long longStartTime = Long.parseLong(cursor.getString(cursor.getColumnIndex(c_TimeFrameColumnNameStartTime)));
		long longEndTime = Long.parseLong(cursor.getString(cursor.getColumnIndex(c_TimeFrameColumnNameEndTime)));
		
		TimeInWeek startTime = TimeInWeek.FromDateInMilliseconds(longStartTime);
		TimeInWeek endTime = TimeInWeek.FromDateInMilliseconds(longEndTime);
		return new TimeFrame(id, startTime, endTime);
	}
	
	/**
	 * Initializes the new instance 
	 * @param startTime the start time
	 * @param endTime the end time
	 */
	private void Initialize(TimeInWeek startTime, TimeInWeek endTime) {
		m_StartTime = startTime;
		m_EndTime = endTime;
	}
	
	/**
	 * @return the calculated date of the start day and time
	 */
	private Date CalculateStartDate() {
		return m_StartTime.CalculateDate(false);
	}
	
	/**
	 * @return the calculated date of the end day and time
	 */
	private Date CalculateEndDate() {
		Date calculated = m_EndTime.CalculateDate(false);
		
		if (calculated.getTime() >= CalculateStartDate().getTime()) {
			return calculated;
		}
		return m_EndTime.CalculateDate(true);
	}
	
	// IDataEntity implementation
	
	@Override
	public void Refresh(Context context) throws DataEntityDoesNotExistException {
		TimeFrame tempTimeFrame = GetTimeFrameById(context, GetId());
		m_StartTime = tempTimeFrame.GetStartTime();
		m_EndTime = tempTimeFrame.GetEndTime();
	}
	
	// DataEntity implementation

	@Override
	protected Hashtable<String, ContentValues> GetRecordValues() {
		ContentValues values = new ContentValues();
		values.put(c_TimeFrameColumnNameStartTime, String.valueOf(CalculateStartDate().getTime()));
		values.put(c_TimeFrameColumnNameEndTime, String.valueOf(CalculateEndDate().getTime()));
		
		Hashtable<String, ContentValues> valuesTable = new Hashtable<String, ContentValues>();
		valuesTable.put(TimeFrameTableName, values);
		return valuesTable;
	}

	@Override
	protected Set<String> GetTableNames() {
		Set<String> tableNames = new HashSet<String>();
		tableNames.add(TimeFrameTableName);
		return tableNames;
	}
	
	// Object implementation
	
	@Override
	public String toString() {
		return String.format("%s - %s", m_StartTime.toString(), m_EndTime.toString());
	}
	
	@Override
	public int hashCode() {
		return toString().hashCode();
	}
}
